package org.tbstore.client;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

import java.util.List;
import java.util.zip.Deflater;

import org.apache.log4j.Logger;

public class MessageSendPacket {
	private static final Logger log                          = Logger.getLogger(MessageSendPacket.class);
    private int                 version;
    private int                 hash_code;
    private int                 len;
    private byte                cmd;
    private byte                key_len;
    private int                 data_len;
    private int                 max_age;
    private byte                area;
    private byte[]              key;
    private Object              data;

    public MessageSendPacket() {
        version   = StoreConstant.STORE_VERSION;
        hash_code = 0;
        len       = 0;
        area      = 0x0;
        cmd       = 0;
        key_len   = 0;
        data_len  = 0;
        max_age   = 0;
        key       = null;
        data      = null;
    }

    /**
     * @param max_age The max_age to set.
     */
    public void setMax_age(int max_age) {
        this.max_age = max_age;
    }

    /**
     * @param data The data to set.
     */
    public void setData(Object data) {
        this.data = data;
    }

    int sumHashCode(byte[] key) {
        if (key == null) {
            return 0;
        }

        int hash = 0;
        int len = key.length;

        for (int i = 0; i < len; i++) {
            hash = (hash * 33) + key[i];
        }

        return hash;
    }

    /**
     * @param key The key to set.
     */
    public void setKey(byte[] key) {
        this.key  = key;
        key_len   = (byte) (key.length & 0xff);
        hash_code = sumHashCode(key);
    }

    /**
     * @param cmd The cmd to set.
     */
    public void setCmd(int cmd) {
        this.cmd = (byte) (cmd & 0xff);
    }

    public byte[] compressData(byte[] input) throws IOException {
        Deflater compressor = new Deflater();

        compressor.setLevel(Deflater.BEST_COMPRESSION);
        compressor.setInput(input);
        compressor.finish();

        ByteArrayOutputStream bos = new ByteArrayOutputStream(input.length);
        byte[]                buf = new byte[8192];

        while (!compressor.finished()) {
            int count = compressor.deflate(buf);

            bos.write(buf, 0, count);
        }

        compressor.end();
        return bos.toByteArray();
    }

    public byte[] convertBytes(int i) {
        byte[] b = new byte[4];

        b[0] = (byte) (i & 0xff);
        b[1] = (byte) ((i & 0xff00) >> 8);
        b[2] = (byte) ((i & 0xff0000) >> 16);
        b[3] = (byte) ((i & 0xff000000) >> 24);
        return b;
    }

    public byte[] convertTwoBytes(int i) {
        byte[] b = new byte[2];

        b[0] = (byte) (i & 0xff);
        b[1] = (byte) ((i & 0xff00) >> 8);
        return b;
    }

    public byte[] getBytes() throws IOException {
        if ((key_len <= 0) || (cmd == 0)) {
            log.warn("key or cmd unset.");
            return null;
        }

        byte[] bytes    = null;
        int    comprlen = 0;

        if (data != null) {
            ByteArrayOutputStream objectStream = new ByteArrayOutputStream();
            ObjectOutputStream    s = new ObjectOutputStream(objectStream);

            s.writeObject(data);
            bytes = objectStream.toByteArray();
            s.close();

            data_len = bytes.length;

            if (data_len > StoreConstant.MAX_DATA_LEN) {
                return null;
            }

            if (data_len >= StoreConstant.MIN_COMPRESS_LEN) {
                try {
                    byte[] bytes1 = compressData(bytes);

                    comprlen = data_len;
                    bytes    = bytes1;
                    data_len = bytes.length;
                } catch (IOException e) {
                    log.warn("compress data error.", e);
                }
            }

            data_len += 8;
        }

        len = key_len + data_len + StoreConstant.HEADER_LENGTH;

        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

        outputStream.write(convertTwoBytes(version));
        outputStream.write(convertTwoBytes(hash_code));
        outputStream.write(convertBytes(len));
        outputStream.write(cmd);
        outputStream.write(convertTwoBytes(key_len));
        outputStream.write(convertBytes(data_len));
        outputStream.write(area);
        outputStream.write(key);

        if (data_len > 0) {
            outputStream.write(convertBytes(max_age));
            outputStream.write(convertBytes(comprlen));
            outputStream.write(bytes);
        }

        return outputStream.toByteArray();
    }

    public byte[] getIncCount(List list)  throws IOException {
    	int list_count = 0;
    	ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    	
    	cmd = StoreConstant.CMD_INCNUM;
    	key_len = 0;
    	area = -1;
    	hash_code = list.hashCode();
    	
    	ByteArrayOutputStream objectStream = new ByteArrayOutputStream();
    	for(int i=0; i<list.size(); i++) {
    		Object obj = list.get(i);
    		if (obj instanceof Integer) {
    			objectStream.write((byte)4);
    			objectStream.write(convertBytes(((Integer)obj).intValue()));
    			list_count ++;
    		}
    	}
    	
    	data_len = list_count * 5 + 4;
    	len = key_len + data_len + StoreConstant.HEADER_LENGTH;
    	
    	outputStream.write(convertTwoBytes(version));
        outputStream.write(convertTwoBytes(hash_code));
        outputStream.write(convertBytes(len));
        outputStream.write(cmd);
        outputStream.write(convertTwoBytes(key_len));
        outputStream.write(convertBytes(data_len));
        outputStream.write(area);
        
        outputStream.write(convertBytes(list_count));
        outputStream.write(objectStream.toByteArray());

    	return outputStream.toByteArray();
    }
    /**
     * @return Returns the hash_code.
     */
    public int getHashCode() {
        return (hash_code & 0xffff);
    }
}
